#include "FBO.hpp"
#include <Graphics\OpenGLTools.hpp>

namespace zzz {
FBO::FBO()
:fbo_(0)
{
}

FBO::~FBO()
{
  glDeleteFramebuffers(1, &fbo_);
}

void FBO::Bind()
{
  GLBindFrameBuffer::Set(GetID());
}

void FBO::Unbind()
{
  GLBindFrameBuffer::Restore();
}

void FBO::Disable()
{
  GLBindFrameBuffer::Set(0);
}

void FBO::AttachTexture(GLenum texTarget, GLuint texId, GLenum attachment, int mipLevel, int zSlice)
{
  Bind();
  CHECK_GL_ERROR();
  switch(texTarget) {
  case GL_TEXTURE_1D:
    glFramebufferTexture1D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_1D, texId, mipLevel);
    CHECK_GL_ERROR();
    break;
  case GL_TEXTURE_3D:
    glFramebufferTexture3D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_3D, texId, mipLevel, zSlice);
    CHECK_GL_ERROR();
    break;
  case GL_TEXTURE_2D:
    glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texId, mipLevel);
    CHECK_GL_ERROR();
    break;
  default:
    // For GL_TEXTURE_CUBE_MAP_X
    glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, texTarget, texId, mipLevel);
    CHECK_GL_ERROR();
  }
  Unbind();
  CHECK_GL_ERROR();
}

void FBO::AttachTexture(Texture &tex, GLenum attachment /*= GL_COLOR_ATTACHMENT0_EXT*/, int mipLevel /*= 0*/, int zSlice /*= 0 */)
{
  AttachTexture(tex.tex_target_, tex.GetID(), attachment, mipLevel, zSlice);
}

void FBO::AttachTextures(int numTextures, GLenum texTarget[], GLuint texId[], GLenum attachment[], int mipLevel[], int zSlice[])
{
  for(int i = 0; i < numTextures; ++i) {
    AttachTexture(texTarget[i], texId[i],
                   attachment ? attachment[i] : (GL_COLOR_ATTACHMENT0+ i),
                   mipLevel ? mipLevel[i] : 0,
                   zSlice ? zSlice[i] : 0);
  }
}

void
FBO::AttachRenderBuffer(GLuint buffId, GLenum attachment)
{
  Bind();
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, buffId);
  Unbind();
}

void
FBO::AttachRenderBuffers(int numBuffers, GLuint buffId[], GLenum attachment[])
{
  for(int i = 0; i < numBuffers; ++i) {
    AttachRenderBuffer(buffId[i], attachment ? attachment[i] : (GL_COLOR_ATTACHMENT0+ i));
  }
}

void
FBO::Unattach(GLenum attachment)
{
  Bind();
  GLenum type = GetAttachedType(attachment);

  switch(type) {
  case GL_NONE:
    break;
  case GL_RENDERBUFFER_EXT:
    AttachRenderBuffer(0, attachment);
    break;
  case GL_TEXTURE:
    AttachTexture(GL_TEXTURE_2D, 0, attachment);
    break;
  default:
    cerr << "FramebufferObject::unbind_attachment ERROR: Unknown attached resource type\n";
  }
  Unbind();
}

void FBO::UnattachAll()
{
  int numAttachments = GetMaxColorAttachments();
  for(int i = 0; i < numAttachments; ++i) {
    Unattach(GL_COLOR_ATTACHMENT0_EXT + i);
  }
}

GLint FBO::GetMaxColorAttachments()
{
  GLint maxAttach = 0;
  glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxAttach);
  return maxAttach;
}

bool FBO::CheckStatus()
{
  Bind();

  bool isOK = false;

  GLenum status;
  status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  switch(status) {
  case GL_FRAMEBUFFER_COMPLETE: // Everything's OK
    isOK = true;
    break;
  case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n";
    isOK = false;
    break;
  case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n";
    isOK = false;
    break;
  case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n";
    isOK = false;
    break;
  case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n";
    isOK = false;
    break;
  case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER\n";
    isOK = false;
    break;
  case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER\n";
    isOK = false;
    break;
  case GL_FRAMEBUFFER_UNSUPPORTED:
    ZLOGE << "CheckStatus() ERROR: GL_FRAMEBUFFER_UNSUPPORTED\n";
    isOK = false;
    break;
  default:
    ZLOGE << "CheckStatus() ERROR: Unknown ERROR\n";
    isOK = false;
  }

  Unbind();
  return isOK;
}

/// Accessors
GLenum FBO::GetAttachedType(GLenum attachment)
{
  // Returns GL_RENDERBUFFER or GL_TEXTURE
  Bind();
  GLint type = 0;
  glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER_EXT, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT, &type);
  Unbind();
  return GLenum(type);
}

GLuint FBO::GetAttachedId(GLenum attachment)
{
  Bind();
  GLint id = 0;
  glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT, &id);
  Unbind();
  return GLuint(id);
}

GLint FBO::GetAttachedMipLevel(GLenum attachment)
{
  Bind();
  GLint level = 0;
  glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT, &level);
  Unbind();
  return level;
}

GLint FBO::GetAttachedCubeFace(GLenum attachment)
{
  Bind();
  GLint level = 0;
  glGetFramebufferAttachmentParameterivEXT(GL_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT, &level);
  Unbind();
  return level;
}

GLint FBO::GetAttachedZSlice(GLenum attachment)
{
  Bind();
  GLint slice = 0;
  glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT, &slice);
  Unbind();
  return slice;
}

GLuint FBO::GetID()
{
  if (!IsValid()) {
    glGenFramebuffers(1, &fbo_);
    CHECK_GL_ERROR();
    ZCHECK_NOT_ZERO(fbo_);
  }
  return fbo_;
}

bool FBO::IsValid()
{
  bool valid=(glIsFramebuffer(fbo_)==GL_TRUE);
  CHECK_GL_ERROR()
  return valid;
}

};  // namespace zzz


